package com.tumblr.logico2012.gestures;

import processing.core.PApplet;
import processing.core.PVector;
import SimpleOpenNI.SimpleOpenNI;

/**
 * This class implements both the swipe right and swipe left gestures for both hands.
 * The interface IGesture is not fully implemented: the events gestureStarted, gestureEnded
 * and gestureAborted are still abstract. They are implemented in the various instances of this
 * gesture used in the program.
 */
public abstract class ISwipeGesture implements IGesture {
	protected int m_userId;
	protected int m_hand;
	protected int m_direction;
	protected boolean m_active;
	protected PVector m_startPosition;
	protected String m_icon;
	protected PApplet p;
	protected boolean m_init;
	
	public final static float START_THRESHOLD_ANGLE_SPEED_XY = -0.15f;
	public final static float START_THRESHOLD_ANGLE_SPEED_XZ = -0.15f;
	public final static float STOP_THRESHOLD_ANGLE_SPEED_XY = -0.08f;
	public final static float STOP_THRESHOLD_ANGLE_SPEED_XZ = 0.08f;
	public final static float[] THRESHOLD_RANGE_ANGLE_SPEED_YZ = new float[] { -0.2f, 0.2f };
	public final static float[] THRESHOLD_RANGE_ANGLE_YZ = new float[] { (float)Math.PI/2, (float)(4*Math.PI/5) };
	public final static int ABORT_THRESHOLD = 70;
	
	public ISwipeGesture(int userId, int hand, int direction, PApplet processor, String icon) {
		m_userId = userId;
		m_hand = hand;
		m_active = false;
		m_startPosition = new PVector();
		p = processor;
		m_icon = icon;
		m_init = false;
		
		if(direction == RIGHT) 	m_direction = 1;
		else					m_direction = -1;
	}
	
	@Override
	public boolean isActive() { return m_active; }
	
	@Override
	public int getUserId() { return m_userId; }
	
	@Override
	public boolean isInit() { return m_init; }
	
	@Override
	public String getIcon() { return m_icon; }
	
	@Override
	public boolean showBorderOnSelect() { return true; }
	
	@Override
	public void init(GestureManager manager) {
		// for this gesture, we only need the segment elbow-hand
		manager.enableSegment(m_userId, 
				m_hand == RIGHT ? SimpleOpenNI.SKEL_RIGHT_ELBOW : SimpleOpenNI.SKEL_LEFT_ELBOW,
				m_hand == RIGHT ? SimpleOpenNI.SKEL_RIGHT_HAND : SimpleOpenNI.SKEL_LEFT_HAND);
		m_init = true;
	}

	@Override
	public void setRefractory(GestureManager manager) {
		GestureManager.Segment segment = manager.getSegment(m_userId, 
				m_hand == RIGHT ? SimpleOpenNI.SKEL_RIGHT_ELBOW : SimpleOpenNI.SKEL_LEFT_ELBOW,
				m_hand == RIGHT ? SimpleOpenNI.SKEL_RIGHT_HAND : SimpleOpenNI.SKEL_LEFT_HAND);
		if(segment != null) {
			segment.setRefractory();
		}
	}

	@Override
	public boolean checkForStartEvent(GestureManager manager) {
		GestureManager.Segment segment = manager.getSegment(m_userId, 
				m_hand == RIGHT ? SimpleOpenNI.SKEL_RIGHT_ELBOW : SimpleOpenNI.SKEL_LEFT_ELBOW,
				m_hand == RIGHT ? SimpleOpenNI.SKEL_RIGHT_HAND : SimpleOpenNI.SKEL_LEFT_HAND);
		if(segment != null) {
			if(!m_active) {
				if(segment.m_hasDelta) {
					// Basically, we want the arm moving on the XZ and XY projections, but not on the YZ.
					// We also require the arm to form a certain angle with the body (angle on the YZ projection)
					if(m_direction*segment.m_delta_angles.y > START_THRESHOLD_ANGLE_SPEED_XZ &&
							m_direction*segment.m_delta_angles.x < START_THRESHOLD_ANGLE_SPEED_XY &&
							segment.m_delta_angles.z > THRESHOLD_RANGE_ANGLE_SPEED_YZ[0] &&
							segment.m_delta_angles.z < THRESHOLD_RANGE_ANGLE_SPEED_YZ[1] &&
							segment.m_angles.z > THRESHOLD_RANGE_ANGLE_YZ[0] &&
							segment.m_angles.z < THRESHOLD_RANGE_ANGLE_YZ[1]) {
						m_active = true;
						// we store the position of the hand when the gesture started.
						manager.m_context.getJointPositionSkeleton(m_userId,
								m_hand == RIGHT ? SimpleOpenNI.SKEL_RIGHT_HAND : SimpleOpenNI.SKEL_LEFT_HAND,
										m_startPosition);
						return true;
					}
				}
			}
		}
		
		return false;
	}

	@Override
	public boolean checkForStopEvent(GestureManager manager) {
		GestureManager.Segment segment = manager.getSegment(m_userId, 
				m_hand == RIGHT ? SimpleOpenNI.SKEL_RIGHT_ELBOW : SimpleOpenNI.SKEL_LEFT_ELBOW,
				m_hand == RIGHT ? SimpleOpenNI.SKEL_RIGHT_HAND : SimpleOpenNI.SKEL_LEFT_HAND);
		if(segment != null) {
			if(m_active) {
				if(segment.m_hasDelta) {
					// not(condition of the start event)
					if(m_direction*segment.m_delta_angles.y < START_THRESHOLD_ANGLE_SPEED_XZ ||
							m_direction*segment.m_delta_angles.x > START_THRESHOLD_ANGLE_SPEED_XY ||
							segment.m_delta_angles.z < THRESHOLD_RANGE_ANGLE_SPEED_YZ[0] ||
							segment.m_delta_angles.z > THRESHOLD_RANGE_ANGLE_SPEED_YZ[1] ||
							segment.m_angles.z < THRESHOLD_RANGE_ANGLE_YZ[0] ||
							segment.m_angles.z > THRESHOLD_RANGE_ANGLE_YZ[1]) {
						m_active = false;
						return true;
					}
				} else {
					m_active = false;
					return true;
				}
			}
		}
		return false;
	}

	@Override
	public boolean isAborted(GestureManager manager) {
		GestureManager.Segment segment = manager.getSegment(m_userId, 
				m_hand == RIGHT ? SimpleOpenNI.SKEL_RIGHT_ELBOW : SimpleOpenNI.SKEL_LEFT_ELBOW,
				m_hand == RIGHT ? SimpleOpenNI.SKEL_RIGHT_HAND : SimpleOpenNI.SKEL_LEFT_HAND);
		if(segment != null) {
			if(segment.m_hasPrevious) {
				PVector pos = new PVector();
				manager.m_context.getJointPositionSkeleton(m_userId,
						m_hand == RIGHT ? SimpleOpenNI.SKEL_RIGHT_HAND : SimpleOpenNI.SKEL_LEFT_HAND,
								pos);

				// the gesture is "aborted" is the distance made by the hand is too small.
				// this is not very reliable.
				float L = (float)Math.sqrt(Math.pow(m_startPosition.x-pos.x, 2) + Math.pow(m_startPosition.y-pos.y, 2) + Math.pow(m_startPosition.z-pos.z, 2)); 
				PApplet.println(L);
				if(L > ABORT_THRESHOLD)
					return false;
			}
		}
		return true;
	}
}
